package org.jeecg.modules.online.desform.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.es.JeecgElasticsearchTemplate;
import org.jeecg.common.es.QueryStringBuilder;
import org.jeecg.common.system.query.MatchTypeEnum;
import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.RestUtil;
import org.jeecg.modules.online.desform.constant.EsTypes;
import org.jeecg.modules.online.desform.entity.DesignForm;
import org.jeecg.modules.online.desform.entity.DesignFormData;
import org.jeecg.modules.online.desform.mapper.DesignFormDataMapper;
import org.jeecg.modules.online.desform.service.IDesignFormAuthService;
import org.jeecg.modules.online.desform.service.IDesignFormDataService;
import org.jeecg.modules.online.desform.service.IDesignFormService;
import org.jeecg.modules.online.desform.util.DesformUtils;
import org.jeecg.modules.online.desform.vo.es.EsProperties;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;

/**
 * @Description: 表单设计数据
 * @Author: jeecg-boot
 * @Date: 2019-05-16
 * @Version: V1.0
 */
@Slf4j
@Service("designFormDataServiceImpl")
public class DesignFormDataServiceImpl extends ServiceImpl<DesignFormDataMapper, DesignFormData> implements IDesignFormDataService {

    @Autowired
    private DesignFormDataMapper designFormDataMapper;

    @Autowired
    private IDesignFormService designFormService;

    @Autowired
    private JeecgElasticsearchTemplate jes;

    @Autowired
    private IDesignFormAuthService designFormAuthService;

    @Override
    public boolean saveBatchByImport(DesignForm designForm, Collection<JSONObject> entityList, String token) {
        List<DesignFormData> formData = new ArrayList<>();
        // 请求 header
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType(MediaType.APPLICATION_JSON_UTF8_VALUE));
        headers.set("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
        headers.set("X-Access-Token", token);
        // 遍历导入的数据，插入到数据表和online物理表中
        for (JSONObject entity : entityList) {
            DesignFormData data = new DesignFormData();
            data.setDesformId(designForm.getId());
            data.setDesformCode(designForm.getDesformCode());
            data.setDesformName(designForm.getDesformName());
            data.setOnlineFormCode(designForm.getCgformCode());
            data.setDesformDataJson(entity.toJSONString());
            // 如果绑定了 online 表单，就同步导入到物理表
            // TODO 待优化
            if (StringUtils.isNotBlank(designForm.getCgformCode())) {
                String url = RestUtil.getBaseUrl() + "/online/cgform/api/crazyForm/" + designForm.getCgformCode();
                JSONObject res = RestUtil.request(url, HttpMethod.POST, headers, null, entity, JSONObject.class).getBody();
                if (res != null) {
                    String dataId = res.getString("result");
                    data.setOnlineFormDataId(dataId);
                }
            }
            formData.add(data);
        }
        boolean result = super.saveBatch(formData);
        // 保存成功，同步至 ElasticSearch 索引
        if (result) {
            for (DesignFormData designFormData : formData) {
                DesformUtils.asyncSaveOrUpdateEsIndex(designForm.getDesformCode(), designFormData);
            }
        }
        return result;
    }

    @Override
    public Result<IPage<DesignFormData>> pageList(String desformCode, int pageNo, int pageSize, QueryWrapper<DesignFormData> queryWrapper, JSONArray superQueryList, MatchTypeEnum matchType) {
        Result<IPage<DesignFormData>> result = new Result<>();
        result.setMessage("查询成功");
        Boolean fillFlag = this.fillQueryWrapper(desformCode, queryWrapper, superQueryList, matchType);
        if (fillFlag != null && !fillFlag) {
            result.setMessage("查询成功，但高级查询失败");
        }
        Page<DesignFormData> page = new Page<>(pageNo, pageSize);
        result.setResult(super.page(page, queryWrapper));
        return result;
    }

    @Override
    public Result<IPage<DesignFormData>> queryPage(String desformCode, int pageNo, int pageSize, QueryWrapper<DesignFormData> queryWrapper, String superQuery, String username) throws UnsupportedEncodingException {
        Result<IPage<DesignFormData>> result = new Result<>();
        Result get = this.queryPageOrList(desformCode, queryWrapper, superQuery, pageNo, pageSize, false, username);
        BeanUtils.copyProperties(get, result);
        return result;
    }

    @Override
    public Result<List<DesignFormData>> queryList(String desformCode, QueryWrapper<DesignFormData> queryWrapper, String superQuery, String username) throws UnsupportedEncodingException {
        Result<List<DesignFormData>> result = new Result<>();
        Result get = this.queryPageOrList(desformCode, queryWrapper, superQuery, 0, 0, true, username);
        BeanUtils.copyProperties(get, result);
        return result;
    }

    private Result queryPageOrList(String desformCode, QueryWrapper<DesignFormData> queryWrapper, String superQuery, int pageNo, int pageSize, boolean queryList, String username) throws UnsupportedEncodingException {
        JSONObject superQueryJSON = JSON.parseObject(superQuery);
        MatchTypeEnum matchType = MatchTypeEnum.getByValue(superQueryJSON.getString("matchType"));
        JSONArray superQueryList = this.getSuperQueryList(superQueryJSON);

        // 查询数据权限
        superQueryList = designFormAuthService.queryDataAuth(desformCode, username, queryWrapper, superQueryList);

        Result<Object> result = new Result<>();
        result.setMessage("查询成功");
        Boolean fillFlag = this.fillQueryWrapper(desformCode, queryWrapper, superQueryList, matchType);
        if (fillFlag != null && !fillFlag) {
            result.setMessage("查询成功，但高级查询失败");
        }

        if (queryList) {
            result.setResult(super.list(queryWrapper));
        } else {
            Page<DesignFormData> page = new Page<>(pageNo, pageSize);
            result.setResult(super.page(page, queryWrapper));
        }
        return result;
    }

    /**
     * 从 superQueryJSON 里取出高级查询列表
     *
     * @param superQueryJSON
     * @return
     * @throws UnsupportedEncodingException
     */
    private JSONArray getSuperQueryList(JSONObject superQueryJSON) throws UnsupportedEncodingException {
        String superQueryParams = superQueryJSON.getString("superQueryParams");
        JSONArray superQueryList = new JSONArray();
        if (StringUtils.isNotBlank(superQueryParams)) {
            superQueryParams = URLDecoder.decode(superQueryParams, "UTF-8");
            superQueryList = JSON.parseArray(superQueryParams);
        }
        return superQueryList;
    }

    /**
     * 通过高级查询列表，填充 QueryWrapper
     *
     * @param desformCode
     * @param queryWrapper
     * @param superQueryList
     * @param matchType
     * @return
     */
    private Boolean fillQueryWrapper(String desformCode, QueryWrapper<DesignFormData> queryWrapper, JSONArray superQueryList, MatchTypeEnum matchType) {
        if (superQueryList != null && superQueryList.size() != 0) {
            String typeName = "design_form";
            JSONArray must = new JSONArray();
            JSONArray should = new JSONArray();
            Map<String, QueryStringBuilder> builderMap = new HashMap<>();
            // 获取 es 索引字段类型
            Map<String, EsProperties> mappings = jes.getIndexMappingFormat(desformCode, typeName, EsProperties.class);
            log.info("ES索引字段类型: " + JSON.toJSONString(mappings));
            log.info("高级查询列表: " + JSON.toJSONString(superQueryList));
            for (int i = 0; i < superQueryList.size(); i++) {
                JSONObject queryItem = superQueryList.getJSONObject(i);
                String val = queryItem.getString("val");
                String type = queryItem.getString("type");
                String field = queryItem.getString("field");
                // 是否是keyword类型（字符串）
                boolean isKeyword = (!"number".equals(type) && !"date".equals(type) && !"time".equals(type));
                // 获取该字段的实际类型
                EsProperties esProperties = mappings.get(field);
                if (esProperties != null && esProperties.getType() != null) {
                    EsTypes esType = esProperties.getType();
                    // 判断特殊类型，不加 keyword
                    if (esType == EsTypes.DATE
                            // 整数类型
                            || esType == EsTypes.INTEGER
                            || esType == EsTypes.LONG
                            || esType == EsTypes.SHORT
                            || esType == EsTypes.BYTE
                            // 浮点类型
                            || esType == EsTypes.DOUBLE
                            || esType == EsTypes.FLOAT
                            || esType == EsTypes.HALF_FLOAT
                            || esType == EsTypes.SCALED_FLOAT
                            // 逻辑类型
                            || esType == EsTypes.BOOLEAN
                    ) {
                        isKeyword = false;
                    }
                }
                // 字符串类型需要加上 keyword 才能查出来数据
                if (isKeyword) {
                    field += ".keyword";
                }
                QueryRuleEnum rule = QueryRuleEnum.getByValue(queryItem.getString("rule"));
                if (rule != null) {
                    switch (rule) {
                        case GT: // 大于
                            this.addQueryByMatchType(jes.buildRangeQuery(field, val, null, false, false), matchType, must, should);
                            break;
                        case GE: // 大于等于
                            this.addQueryByMatchType(jes.buildRangeQuery(field, val, null, true, false), matchType, must, should);
                            break;
                        case LT: // 小于
                            this.addQueryByMatchType(jes.buildRangeQuery(field, null, val, false, false), matchType, must, should);
                            break;
                        case LE: // 小于等于
                            this.addQueryByMatchType(jes.buildRangeQuery(field, null, val, false, true), matchType, must, should);
                            break;
                        case EQ: // 等于
                            this.addQueryStringByMatchType(builderMap, field, val, matchType, true);
                            break;
                        case NE: // 不等于
                            this.notQueryString(builderMap, field, val);
                            break;
                        case IN: // 在...中
                            String[] valArr = val.split(",");
                            if (valArr.length > 0) {
                                QueryStringBuilder inBuilder = new QueryStringBuilder(field, valArr[0], false);
                                for (int j = 1; j < valArr.length; j++) {
                                    inBuilder.or(valArr[j]);
                                }
                                this.addQueryByMatchType(jes.buildQueryString(inBuilder.toString()), matchType, must, should);
                            }
                            break;
                        case LIKE: // 包含
                            this.addQueryStringByMatchType(builderMap, field, "*" + val + "*", matchType, false);
                            break;
                        case LEFT_LIKE: // 以..结尾
                            this.addQueryStringByMatchType(builderMap, field, "*" + val, matchType, false);
                            break;
                        case RIGHT_LIKE: // 以..开始
                            this.addQueryStringByMatchType(builderMap, field, val + "*", matchType, false);
                            break;
                    }
                }
            }
            for (String key : builderMap.keySet()) {
                String queryString = builderMap.get(key).toString();
                this.addQueryByMatchType(jes.buildQueryString(queryString), matchType, must, should);
            }
            try {
                // 只返回 id 字段，多余的不返回，减轻带宽负担
                List<String> _source = Collections.singletonList("_id");
                JSONObject query = jes.buildQuery(_source, jes.buildBoolQuery(must, null, should), 0, JeecgElasticsearchTemplate.ES_MAX_SIZE);
                JSONObject searchResult = jes.search(desformCode, typeName, query);
                JSONArray records = searchResult.getJSONObject("hits").getJSONArray("hits");
                if (records.size() > 0) {
                    List<String> ids = new ArrayList<>();
                    for (int i = 0; i < records.size(); i++) {
                        JSONObject record = records.getJSONObject(i);
                        ids.add(record.getString("_id"));
                    }
                    queryWrapper.in("id", ids);
                } else {
                    // 没有查询到任何结果，添加一个永远都不成立的条件，使其查询不出来数据
                    queryWrapper.eq("1", "2");
                }
                return true;
            } catch (Exception e) {
                log.error("ES索引数据查询失败：" + e.getMessage(), e);
                return false;
            }
        }
        return null;
    }

    // 根据 matchType 添加查询条件
    private void addQueryByMatchType(Object query, MatchTypeEnum matchType, JSONArray must, JSONArray should) {
        if (matchType == MatchTypeEnum.OR) {
            should.add(query);
        } else {
            must.add(query);
        }
    }

    // 根据 matchType 添加查询条件
    private void addQueryStringByMatchType(Map<String, QueryStringBuilder> map, String field, String value, MatchTypeEnum matchType, boolean addQuot) {
        QueryStringBuilder builder = getQueryString(map, field, value, false, addQuot);
        if (builder != null) {
            if (matchType == MatchTypeEnum.OR) {
                builder.or(value, addQuot);
            } else {
                builder.and(value, addQuot);
            }
        }
    }

    private void andQueryString(Map<String, QueryStringBuilder> map, String field, String value) {
        QueryStringBuilder builder = getQueryString(map, field, value);
        if (builder != null) {
            builder.and(value);
        }
    }

    private void notQueryString(Map<String, QueryStringBuilder> map, String field, String value) {
        QueryStringBuilder builder = getQueryString(map, field, value, true, true);
        if (builder != null) {
            builder.not(value);
        }
    }

    private QueryStringBuilder getQueryString(Map<String, QueryStringBuilder> map, String field, String value) {
        return getQueryString(map, field, value, false, true);
    }

    private QueryStringBuilder getQueryString(Map<String, QueryStringBuilder> map, String field, String value, boolean not, boolean addQuot) {
        QueryStringBuilder builder = map.get(field);
        if (builder == null) {
            builder = new QueryStringBuilder(field, value, not, addQuot);
            map.put(field, builder);
            return null;
        }
        return builder;
    }

    @Override
    public List<DesignFormData> selectByMainId(String mainId) {
        return designFormDataMapper.selectByMainId(mainId);
    }

    @Override
    public Result addOne(DesignFormData designFormData) {
        Result<String> result = new Result<>();
        if (designFormData == null) {
            result.error500("designFormData 不能为空！");
            return result;
        }
        try {

            String desformCode = designFormData.getDesformCode();
            if (StringUtils.isEmpty(desformCode)) {
                return Result.error("必须传递 desformCode 参数");
            }

            // 根据code查询出desform的数据
            DesignForm designForm = designFormService.getByCode(desformCode);
            if (designForm == null) {
                return Result.error("不存在的 desformCode");
            }

            // 判断是否是子表
            if (CommonConstant.DESIGN_FORM_TYPE_SUB.equals(designForm.getDesformType())) {
                designFormData.setDesformId(designForm.getParentId());
                designFormData.setDesformCode(designForm.getParentCode());
            } else {
                designFormData.setDesformId(designForm.getId());
            }
            designFormData.setDesformName(designForm.getDesformName());

            super.save(designFormData);

            // 异步保存或更新 ElasticSearch 索引数据
            DesformUtils.asyncSaveOrUpdateEsIndex(designForm.getDesformCode(), designFormData);

            // 返回新增的id
            result.setResult(designFormData.getId());
            result.success("添加成功！");
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            result.error500("操作失败");
        }
        return result;
    }

    @Override
    public Result editOne(DesignFormData designFormData) {
        Result<String> result = new Result<>();
        DesignFormData designFormEntity = super.getById(designFormData.getId());
        if (designFormEntity == null) {
            result.error500("未找到对应数据!");
        } else {
            designFormData.setDesformId(null);
            designFormData.setDesformCode(null);
            super.updateById(designFormData);

            // 异步保存或更新 ElasticSearch 索引数据
            DesformUtils.asyncSaveOrUpdateEsIndex(designFormEntity.getDesformCode(), designFormData);

            result.setResult(designFormData.getId());
            result.success("修改成功!");
        }
        return result;
    }

    @Override
    public Result deleteOne(DesignFormData designFormData, String token) {
        designFormData = super.getById(designFormData.getId());
        if (designFormData == null) {
            return Result.error("未找到对应实体");
        } else {
            super.removeById(designFormData.getId());
            syncRemoveOnlineData(designFormData, token);
            // 异步删除 ElasticSearch 索引数据
            DesformUtils.asyncDeleteEsIndex(designFormData.getDesformCode(), designFormData);
            return Result.ok("删除成功");
        }
    }

    @Override
    public Result deleteOne(String id, String token) {
        DesignFormData designFormData = new DesignFormData();
        designFormData.setId(id);
        return this.deleteOne(designFormData, token);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatchMain(List<String> idList, String token) {
        List<DesignFormData> list = baseMapper.selectList(new LambdaQueryWrapper<DesignFormData>().in(DesignFormData::getId, idList));
        for (DesignFormData designFormData : list) {
            designFormDataMapper.deleteById(designFormData.getId());
            syncRemoveOnlineData(designFormData, token);
            // 异步删除 ElasticSearch 索引数据
            DesformUtils.asyncDeleteEsIndex(designFormData.getDesformCode(), designFormData);
        }
    }

    /**
     * 如果关联了online表单，就同步删除
     */
    private void syncRemoveOnlineData(DesignFormData designFormData, String token) {
        String onlineFormCode = designFormData.getOnlineFormCode();
        String onlineDataId = designFormData.getOnlineFormDataId();
        if (StringUtils.isNotBlank(onlineFormCode) && StringUtils.isNotBlank(onlineDataId)) {
            // 请求 header
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.parseMediaType(MediaType.APPLICATION_JSON_UTF8_VALUE));
            headers.set("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
            headers.set("X-Access-Token", token);
            // 请求地址
            String url = RestUtil.getBaseUrl() + String.format("/online/cgform/api/formByCode/%s/%s", onlineFormCode, onlineDataId);
            RestUtil.request(url, HttpMethod.DELETE, headers, null, null, JSONObject.class);
        }
    }

}
